﻿namespace DimensionsHelper.Client.WinUi.Controls;

public partial class BorderGrid : Grid
{
    private static readonly ConditionalWeakTable<object, GridStatus> StatusCache = [];


    public static readonly DependencyProperty BorderColorProperty = DependencyProperty.Register(
        nameof(BorderColor),
        typeof(Color),
        typeof(BorderGrid),
        new PropertyMetadata(Colors.LightGray));


    public static readonly DependencyProperty ThinBorderThicknessProperty = DependencyProperty.Register(
        nameof(ThinBorderThickness),
        typeof(double),
        typeof(BorderGrid),
        new PropertyMetadata(1d, OnGridPropertyChanged));


    public static readonly DependencyProperty NormalBorderThicknessProperty = DependencyProperty.Register(
        nameof(NormalBorderThickness),
        typeof(double),
        typeof(BorderGrid),
        new PropertyMetadata(2d, OnGridPropertyChanged));


    public static readonly DependencyProperty MediumBorderThicknessProperty = DependencyProperty.Register(
        nameof(MediumBorderThickness),
        typeof(double),
        typeof(BorderGrid),
        new PropertyMetadata(2.5d, OnGridPropertyChanged));


    public static readonly DependencyProperty ThickBorderThicknessProperty = DependencyProperty.Register(
        nameof(ThickBorderThickness),
        typeof(double),
        typeof(BorderGrid),
        new PropertyMetadata(3d, OnGridPropertyChanged));

    public BorderGrid()
    {
        ColumnSpacing = 0;
        Ranges = new BorderGridRangeCollection(this);
        Loaded += OnLoaded;
        SizeChanged += OnSizeChanged;
    }

    public BorderGridRangeCollection Ranges { get; }

    public int Rows => RowDefinitions.Count;

    public int Columns => ColumnDefinitions.Count;


    public Color BorderColor
    {
        get => (Color)GetValue(BorderColorProperty);
        set => SetValue(BorderColorProperty, value);
    }


    public double ThinBorderThickness
    {
        get => (double)GetValue(ThinBorderThicknessProperty);
        set => SetValue(ThinBorderThicknessProperty, value);
    }


    public double NormalBorderThickness
    {
        get => (double)GetValue(NormalBorderThicknessProperty);
        set => SetValue(NormalBorderThicknessProperty, value);
    }


    public double MediumBorderThickness
    {
        get => (double)GetValue(MediumBorderThicknessProperty);
        set => SetValue(MediumBorderThicknessProperty, value);
    }


    public double ThickBorderThickness
    {
        get => (double)GetValue(ThickBorderThicknessProperty);
        set => SetValue(ThickBorderThicknessProperty, value);
    }


    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        DispatcherQueue.TryEnqueue(() =>
        {
            UpdateLayout();

            if (Ranges.IsInitialized)
            {
                RefreshRangeStatus();
                RefreshBorder();
            }
            else
            {
                Ranges.Initialize();
            }

            StatusCache.TryAdd(this, new GridStatus(
                RowDefinitions.Select(r => r.Height).ToArray(),
                ColumnDefinitions.Select(c => c.Width).ToArray(),
                new BitArray(Rows),
                new BitArray(Columns)));

            BorderGridRange.InitializeHiddenAndMergedProperty();
        });
    }


    public void RefreshRangeStatus()
    {
        foreach (BorderGridRange range in Ranges)
        {
            range.RefreshMergedFlags(range.IsMerged, true);
        }
    }


    public void RefreshBorder()
    {
        if (!IsLoaded)
        {
            return;
        }

        Ranges.UpdateVisualSize();
        Ranges.UpdateBorder();
    }


    private void OnSizeChanged(object sender, SizeChangedEventArgs e) => RefreshBorder();


    internal void ChangeRowVisibility(int row, bool isHidden)
    {
        if (row < 0 || row >= Rows ||
            !StatusCache.TryGetValue(this, out GridStatus? status) ||
            status.RowStatus.Get(row) == isHidden)
        {
            return;
        }

        RowDefinitions[row].Height = isHidden ? new GridLength(0) : status.RowLength[row];
        status.RowStatus.Set(row, isHidden);
    }


    internal void ChangeColumnVisiblity(int column, bool isHidden)
    {
        if (column < 0 || column >= Columns ||
            !StatusCache.TryGetValue(this, out GridStatus? status) ||
            status.ColumnStatus.Get(column) == isHidden)
        {
            return;
        }

        ColumnDefinitions[column].Width = isHidden ? new GridLength(0) : status.ColumnWidth[column];
        status.ColumnStatus.Set(column, isHidden);
    }


    private static void OnGridPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        BorderGrid grid = (BorderGrid)d;

        if (!grid.IsLoaded)
        {
            return;
        }

        grid.Ranges.UpdateBorder();
    }

    private record GridStatus(
        GridLength[] RowLength,
        GridLength[] ColumnWidth,
        BitArray RowStatus,
        BitArray ColumnStatus);
}